/*
 * Copyright (c) 2003, 2004, 2005, 2006 Israfil Consulting Services Corporation
 * Copyright (c) 2003, 2004, 2005, 2006 Christian Edward Gruber
 * All Rights Reserved
 * 
 * This software is licensed under the Berkeley Standard Distribution license,
 * (BSD license), as defined below:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Israfil Consulting Services nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 * 
 * $Id: MockResultSetMetaData.java 13 2006-01-27 23:45:36Z cgruber $
 */
package net.israfil.foundation.mock.sql;

import java.math.BigDecimal;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * An implementation of DatabaseMetaData that partners with ResultSet to 
 * provide database-disconnected fixed results for testing via the JDBC 
 * APIs.
 * 
 * @author <a href="mailto:cgruber@israfil.net">Christian Edward Gruber </a>
 */
public class MockResultSetMetaData implements ResultSetMetaData {
	
	public static int ZERO_ORDER_LIST_OFFSET = 1;
	List<ColSpec> columns;
	
	MockResultSetMetaData(String ... columns) {
		if (columns == null) throw new IllegalArgumentException("Cannot construct a MockResultSet with no columns.");
		ColSpec[] colspecs = new ColSpec[columns.length];
		for (int i = 0 ; i < columns.length ; i++) {
			colspecs[i] = new ColSpec(columns[i]);
		}
		prepColumns(colspecs);
	}
	
	MockResultSetMetaData(ColSpec ... columns) { 
		prepColumns(columns);
	}
	
	private void prepColumns(ColSpec ... columns) {
		if (columns == null || columns.length <= 0) 
			throw new IllegalArgumentException("Cannot construct a MockResultSet with no columns.");
		this.columns = new ArrayList<ColSpec>(Arrays.asList(columns)); 
	}
	
	public void finalize() throws Throwable {
		columns = null;
	}
	
	/**
	 * Given a column name, this method returns the number of the column, or 0 if 
	 * no such column exists.
	 * 
	 * @param name
	 * @return
	 */
	public int getColumnNumber(String name) {
		for (int i = 1; i <= columns.size(); i++)
			if (columns.get(i-ZERO_ORDER_LIST_OFFSET).name.equalsIgnoreCase(name)) return i;
		return 0;
	}
	
	public String getCatalogName(int column) throws SQLException {
		return "";
	}

	public String getColumnClassName(int column) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public int getColumnCount() throws SQLException {
		return columns.size();
	}

	public int getColumnDisplaySize(int column) throws SQLException {
		return columns.get(column-ZERO_ORDER_LIST_OFFSET).width;
	}

	public String getColumnLabel(int column) throws SQLException {
		if (column < 1) throw new IllegalArgumentException("Column number must be greater than 0.");
		return columns.get(column-ZERO_ORDER_LIST_OFFSET).name;
	}

	public String getColumnName(int column) throws SQLException {
		if (column < 1) throw new IllegalArgumentException("Column number must be greater than 0.");
		return columns.get(column-ZERO_ORDER_LIST_OFFSET).name;
	}

	public int getColumnType(int column) throws SQLException {
		return columns.get(column-ZERO_ORDER_LIST_OFFSET).type.sqltype;
	}

	public String getColumnTypeName(int column) throws SQLException {
			return columns.get(column).type.name();
	}

	public int getPrecision(int column) throws SQLException {
		return columns.get(column-ZERO_ORDER_LIST_OFFSET).precision;
	}

	public int getScale(int column) throws SQLException {
		return columns.get(column-ZERO_ORDER_LIST_OFFSET).scale;
	}

	public String getSchemaName(int column) throws SQLException {
		return "";
	}

	public String getTableName(int column) throws SQLException {
		return "";
	}

	public boolean isAutoIncrement(int column) throws SQLException {
		return false;
	}

	public boolean isCaseSensitive(int column) throws SQLException {
		return false;
	}

	public boolean isCurrency(int column) throws SQLException {
		return false;
	}

	public boolean isDefinitelyWritable(int column) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public int isNullable(int column) throws SQLException {
		return columns.get(column-ZERO_ORDER_LIST_OFFSET).nullable ? ResultSetMetaData.columnNullable : ResultSetMetaData.columnNoNulls;
	}

	public boolean isReadOnly(int column) throws SQLException {
		return true;
	}

	public boolean isSearchable(int column) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public boolean isSigned(int column) throws SQLException {
		return false;
	}

	public boolean isWritable(int column) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}
	
	public static class ColSpec {
		public final String name;
		public final DBType type;
		public final int width;
		public final int precision;
		public final int scale;
		public final boolean nullable;
		
		public ColSpec(String name) {
			this.name = name;
			this.type = DBType.OBJECT;
			this.width = 0;
			this.precision = 0;
			this.scale = 0;
			this.nullable = true;
		}
		public ColSpec(String name, boolean nullable) {
			this.name = name;
			this.type = DBType.OBJECT;
			this.width = 0;
			this.precision = 0;
			this.scale = 0;
			this.nullable = nullable;
		}
		public ColSpec(String name, DBType type) {
			this.name = name;
			this.type = type;
			this.width = 0;
			this.precision = 0;
			this.scale = 0;
			this.nullable = true;
		}
		public ColSpec(String name, DBType type, boolean nullable) {
			this.name = name;
			this.type = type;
			this.width = 0;
			this.precision = 0;
			this.scale = 0;
			this.nullable = nullable;
		}
		public ColSpec(String name, DBType type, int width) {
			this.name = name;
			this.type = type;
			this.width = width;
			this.precision = 0;
			this.scale = 0;
			this.nullable = true;
		}
		public ColSpec(String name, DBType type, int precision, int scale) {
			this.name = name;
			this.type = type;
			this.precision = precision;
			this.scale = scale;
			this.width = 0;
			this.nullable = true;
		}
		public ColSpec(String name, DBType type, int width, boolean nullable) {
			this.name = name;
			this.type = type;
			this.width = width;
			this.precision = 0;
			this.scale = 0;
			this.nullable = nullable;
		}
		public ColSpec(String name, DBType type, int precision, int scale, boolean nullable) {
			this.name = name;
			this.type = type;
			this.precision = precision;
			this.scale = scale;
			this.width = 0;
			this.nullable = nullable;
		}
	}

	public enum DBTypeGroup { 
		Time(0), Number(1), String(2), Complex(3), Other(4); 
		public int value; 
		private DBTypeGroup(int i) {
			value = i;
		}
	}

	public enum DBType {
		BLOB(byte[].class,Types.BLOB,DBTypeGroup.Other),
		BIT(boolean.class,Types.BIT,DBTypeGroup.Number),
		CHAR(char.class,Types.CHAR,DBTypeGroup.Number),
		DECIMAL(BigDecimal.class,Types.DECIMAL,DBTypeGroup.Number),
		FLOAT(float.class,Types.FLOAT,DBTypeGroup.Number),
		DOUBLE(double.class,Types.DOUBLE,DBTypeGroup.Number),
		SHORT(short.class,Types.SMALLINT,DBTypeGroup.Number),
		INTEGER(int.class,Types.INTEGER,DBTypeGroup.Number),
		LONG(long.class,Types.BIGINT,DBTypeGroup.Number),
		BYTE(byte.class,Types.TINYINT,DBTypeGroup.Number),
		STRING(String.class,Types.VARCHAR,DBTypeGroup.String),
		TIMESTAMP(Timestamp.class,Types.TIMESTAMP,DBTypeGroup.Time),
		OBJECT(Object.class,Types.JAVA_OBJECT,DBTypeGroup.Complex);
	
        public final Class<?>    javaClass;
        public final int sqltype;
        public final DBTypeGroup group;
        private DBType(Class<?> javaClass, int sqltype, DBTypeGroup group) {
        	this.javaClass = javaClass;
        	this.sqltype = sqltype;
        	this.group = group;
        }
        public static DBType valueOf(int sqlType) {
    		switch (sqlType) {
				case Types.BLOB:
				case Types.CLOB:
				case Types.BINARY:  
				case Types.VARBINARY:
				case Types.LONGVARBINARY: return DBType.BLOB;
					
				case Types.BIGINT: return DBType.LONG;
				
				case Types.BOOLEAN:
				case Types.BIT: return DBType.BIT;
	
				case Types.CHAR: return DBType.CHAR;
				case Types.DOUBLE: return DBType.DOUBLE;
				case Types.FLOAT: return DBType.FLOAT;
				case Types.INTEGER: return DBType.INTEGER;
				case Types.SMALLINT: return DBType.SHORT;
				case Types.TINYINT: return DBType.BYTE;
					
				case Types.NUMERIC:
				case Types.REAL:
				case Types.DECIMAL: return DBType.DECIMAL;
				
				case Types.VARCHAR: 
				case Types.LONGVARCHAR: return DBType.STRING;
	
				case Types.DATE:
				case Types.TIME:
				case Types.TIMESTAMP: return DBType.TIMESTAMP;
		
				case Types.JAVA_OBJECT: 
				case Types.ARRAY:
				case Types.STRUCT: return DBType.OBJECT;
		
				case Types.REF:
				case Types.DISTINCT:
				case Types.NULL:
				case Types.OTHER:			
				case Types.DATALINK:
				default:
					throw new IllegalArgumentException("Unsupported JDBC type");
    		}
        	
        }
	}
}
